CSS Modules 认识与实践

  乐弋

第一次看到这个是在@勾股的微博上,当时想的是不是w3c又有什么草案了 ,css也可以有“作用域”的概念了。当仔细看了之后,原来并非如此。

简介

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default

简而言之,它并不是一个官方的草案或者标准,而是在代码打包阶段的一个css前后处理器:可以将样式文件中的类名和选择器变得模块化(也就是只对该模块有效)。

它的核心概念就是:locan scope by default /局部作用域,我此时此刻联想到了”菊”部。。

更直接的说,其实它就是一个打包插件,在打包css代码的时候会帮你做些事情:

  • 让使用者肆无忌惮的去写自己的css代码,不用担心会影响到其他样式。这个在多人合作的时候会大大减少你的烦恼和时间。
  • 让css代码无忧无虑的跨模块使用
  • 让css代码高度可复用

命名

通过Webpack或者Browserify,每个模块所用过的css 都会被单独编译打包,所以你可以在样式表中用通用的名字来命名。

举个例子,比如我们要制作一个按钮,它有两个状态:enabled和disabled。通常会这样命名和使用:

Before CSS Modules:button.css

1
2
3
.button { /* all styles for Normal */ }
.button--disabled { /* overrides for Disabled */ }
.button--normal { /* overrides for normal */ }
1
<button class="button button--enabled">enabled</button>

这是传统的bem风格,但bem有个缺点:嵌套的让人懵逼了。。。

With CSS Modules : button.css

1
2
.disabled { /* all styles for Disabled */ }
.normal { /* all styles for normal */ }

只需在样式中写最通用的命名。在所写的js模块中,引用css样式:

1
2
3
import styles from './button.css';

buttonElem.outerHTML = `<button class=${styles.normal}>按钮</button>`

虽然它是通用命名,但经过编译后它真正的class一定是独一无二的。这就是css modules为你所做的。

1
2
3
<button class="components_submit_button__normal__abc5436">
Processing...
</button>

这里有个需要注意的地方:就是不要写base+overrides风格,用过空格隔开,比如这样:

1
`class=${[styles.normal, styles['in-progress']].join(" ")}`

css modules 推荐一个类应该有它所有的样式。

组合

如上面所说,css modules 不建议bem的风格,但我们如何复用或者共享一些样式呢?那就是接下来的:

CSS Modules’ most potent weapon, composition:

bem写法相应的变成这样:

1
2
3
4
5
6
7
8
9
10
11
.common {
/* all the common styles you want 基类*/
}
.normal {
composes: common;
/* anything that only applies to Normal */
}
.disabled {
composes: common;
/* anything that only applies to Disabled */
}

看看 最后的输出结果:

1
2
3
4
<button class="components_submit_button__common__abc5436 
components_submit_button__normal__def6547">

按钮
</button>

这样 你不需要 更改或者增加你的类名就可以复用和组合 各种基本样式,是不是很方便?

共享

使用compose来引用其他样式文件,不同于less的@import.

1
2
3
4
5
6
7
/* colors.css */
.primary {
color: #720;
}
.secondary {
color: #777;
}

此时button.css需要引用color.css中的primary颜色:

1
2
3
4
5
.common { /* font-sizes, padding, border-radius */ }
.normal {
composes: common;
composes: primary from "./colors.css";
}

这时候可以看到css modules 真的是把样式表每个样式基类独立出来了。

独立样式

有了compose 我们可以写很多基类,然后引用这些基类组成一个模块所需的样式

1
2
3
4
5
6
.element {
composes: large from "./typography.css";
composes: dark-text from "./colors.css";
composes: padding-all-medium from "./layout.css";
composes: subtle-shadow from "./effect.css";
}

感觉就像写 js最小模块一样。 但你不需要关心逻辑,只需自己组合就好了。

React实践

webpack是目前前端构建打包工具的首选了,react的jsx 语法中的className配合css modules ,这样组合实在是太帅气了。

相信大家的webpack插件都有下面这个,这个也是实现css modules的插件.

1
extract-text-webpack-plugin:"~0.9.1"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var webpack = require('webpack');
var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin");


module.exports = {

module: {
loaders: [
{test: /\.js?$/, loaders: ['react-hot', 'babel'], exclude: /node_modules/},
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'),
}
]
},
entry: {
'bundle':[
"webpack-dev-server/client?http://localhost:8080/",
"webpack/hot/dev-server",
path.resolve(__dirname, './src/index.js')],
},
output: {
path: path.resolve(__dirname, 'dev'),
filename: "[name].js",
libraryTarget: "amd"
},
resolve: {
extensions:['','.js','.json']
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ExtractTextPlugin('[name].css')
],
proxy: {
'/some/path*': {
target: 'https://other-server.example.com',
secure: false,
},
}
}

只需要修改css的loader就可以了。

下面就用button举例吧:

button.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/** button.jsx **/
/**
* Created by Aaronphy on 16/2/17.
*/

import React,{Component} from 'react';
import { render } from 'react-dom';
import styles from './style.css';

export default class Button extends Component {

constructor(){
super();
this.state ={
invalid:false
}
}

handleClick(e){

this.setState({invalid:!this.state.invalid});

}

render(){
const text = this.state.invalid ?'不可点击':'可点击';
const {invalid} = this.state;

return(
<button className={invalid?styles.invalid:styles.normal} onClick = {e=>this.handleClick(e)}>
{text}
</button>

);
}
}

base.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.common {
width: 80px;
height:30px;
border:1px solid #ccc;
border-radius: 8px;
outline:none;
}


.primary {
color:white;
background-color: #37A3EC;
}

.disabled{
color: white;
background-color: #cccccc;
}

style.css

1
2
3
4
5
6
7
.normal{
composes: common primary from "./base.css";
}

.invalid{
composes: common disabled from "./base.css";
}

npm run start 下看看效果

_2016_07_29_2_34_24

7_29_2016_14_35_29

参考
CSS Modules

分享到